package com.tms.ned.controlmonitor;

import java.awt.Component;
import java.util.*;

import com.tms.ned.controlmonitor.adapters.ComponentAdapter;

/*
 * Licensed under the Apache License, Version 2.0;<br> 
 * 
 * You may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless 
 * required by applicable law or agreed to in writing, software distributed under the License 
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and limitations under the License. 
 */

/**
 * A general purpose implementation of the ControlMonitor interface. Adds itself
 * as a ComponentChangeListener to each control that is registered, and re-fires
 * the change event to any of the ControlMonitor's listeners. The purpose of
 * this class is to allow the user to listen to changes to multiple components
 * via a single interface.
 *
 * Every component that has validators is revalidated after every change to
 * every component, because changing the value of one component might make
 * another component invalid. (think of two date fields, where the validator
 * asserts that one date is on or before another, etc.)
 *
 * Components are not directly registered with the ControlMonitor, since each
 * type of control fires different types of events. (DocumentEvent,
 * SelectionEvent, ItemEvent, etc.) Instead, ComponentAdapters are registered
 * with this class, giving each UI component a common interface with which to
 * listen to component changes..
 *
 * @author bshannon
 *
 */
public class DefaultControlMonitor implements ControlMonitor, ComponentChangeListener {

	/** Listeners that listen to our Control Monitor * */
	private Set<ComponentChangeListener> changeListeners = new HashSet<ComponentChangeListener>(0);

	/** A map of component->componentAdapter */
	private Map<Component, ComponentAdapter> componentAdapters = new LinkedHashMap<Component, ComponentAdapter>(0);

	/** A set of invalid component adapters. */
	private Set<ComponentAdapter> invalids = new LinkedHashSet<ComponentAdapter>(0);

	/**
	 * Register a ComponentAdapter with this Control Monitor, it will start to
	 * listen to the component's changes via the adapter.
	 */
	public final void registerComponent(ComponentAdapter adapter) {
		adapter.addChangeListener(this);
		componentAdapters.put(adapter.getComponent(), adapter);
		runValidationChecks();
	}

	/**
	 * Unregister a ComponentAdapter with the Control Monitor.
	 */
	public final void unregisterComponent(ComponentAdapter adapter) {
		adapter.removeChangeListener(this);
		componentAdapters.remove(adapter.getComponent());
		runValidationChecks();
	}

	/**
	 * When any components change, run all validation checks and then fire a
	 * single event for any component. This method fires every time one of the
	 * components registered with us changes. We validate, then turn around and
	 * throw our own event.
	 */
	public final void componentChanged(ComponentChangeEvent event) {
		ComponentAdapter adapter = event.getComponentAdapter();
		runValidationChecks();
		fireComponentChangeEvent(event);
	}

	/**
	 * Clients of the Control Monitor who wish to listen to our events add
	 * themselves here.
	 */
	public final void addComponentChangeListener(ComponentChangeListener listener) {
		changeListeners.add(listener);
	}

	/**
	 * Remove a control monitor listener.
	 */
	public final void removeComponentChangeListener(ComponentChangeListener listener) {
		changeListeners.remove(listener);
	}

	/**
	 * Validates each ComponentAdapter, building and returning the list of invalid controls.
	 */
	protected final void runValidationChecks() {
		invalids.clear();
		for (ComponentAdapter adapter : componentAdapters.values()) {
			adapter.validate();
			if (!adapter.getCurrentValidationStatus().isValid()) {
				invalids.add(adapter);
			}
		}
	}

	/**
	 * Fires a ComponentChangeEvent for every ComponentChangeListener. These are
	 * the listeners listening to this ControlMonitor implementation.
	 *
	 * @param event
	 *            The event from the component adapter.
	 */
	protected final void fireComponentChangeEvent(ComponentChangeEvent event) {
		for (ComponentChangeListener listener : changeListeners) {
			listener.componentChanged(event);
		}
	}

	/**
	 * returns the list of invalid component adapters.
	 */
	public final List<ComponentAdapter> getInvalidControls() {
		return new ArrayList(invalids);
	}

	/**
	 * {@inheritDoc}
	 */
	public final List<ComponentAdapter> getDirtyControls() {
		List<ComponentAdapter> rv = new ArrayList<ComponentAdapter>(0);
		for (ComponentAdapter componentAdapter : componentAdapters.values()) {
			if (componentAdapter.isDirty()) {
				rv.add(componentAdapter);
			}
		}
		return rv;
	}

	/**
	 * {@inheritDoc}
	 */
	public final List<ComponentAdapter> getAllComponentAdapters() {
		List<ComponentAdapter> rv = new ArrayList<ComponentAdapter>();
		rv.addAll(componentAdapters.values());
		return rv;
	}

	/**
	 * resets the initial values to match the current values on each component
	 * adapter.
	 */
	public final void resetInitialValuesToCurrentValues() {
		List<ComponentAdapter> adapters = getAllComponentAdapters();
		for (ComponentAdapter componentAdapter : adapters) {
			componentAdapter.setInitialValue(componentAdapter.getCurrentValue());
		}
	}

}
